package cn.jingyuan.owl.utils.core.bean.copier;

import cn.jingyuan.owl.utils.core.ArrayUtils;
import cn.jingyuan.owl.utils.core.ModifierUtils;
import cn.jingyuan.owl.utils.core.ObjectUtils;
import cn.jingyuan.owl.utils.core.ReflectUtils;
import cn.jingyuan.owl.utils.core.StringUtils;
import cn.jingyuan.owl.utils.core.TypeUtils;
import cn.jingyuan.owl.utils.core.bean.BeanDesc;
import cn.jingyuan.owl.utils.core.bean.BeanUtils;
import cn.jingyuan.owl.utils.core.bean.copier.provider.BeanValueProvider;
import cn.jingyuan.owl.utils.core.bean.copier.provider.MapValueProvider;
import cn.jingyuan.owl.utils.core.collection.CollectionUtils;
import cn.jingyuan.owl.utils.core.convert.Convert;
import cn.jingyuan.owl.utils.core.exceptions.UtilsException;
import cn.jingyuan.owl.utils.core.lang.ParameterizedTypeImpl;
import cn.jingyuan.owl.utils.core.lang.copier.Copier;
import cn.jingyuan.owl.utils.core.map.MapUtils;

import java.io.Serializable;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.lang.reflect.TypeVariable;
import java.util.Collection;
import java.util.HashSet;
import java.util.Map;

/**
 * Bean 拷贝
 *
 * @param <T> 目标对象类型
 */
public class BeanCopier<T> implements Copier<T>, Serializable {

    /** 源对象 */
    private final Object source;

    /** 目标对象 */
    private final T dest;

    /** 目标的类型（用于泛型类注入） */
    private final Type destType;

    /** 拷贝选项 */
    private final CopyOptions copyOptions;

    /**
     * 构造
     *
     * @param source 来源对象，可以是 Bean 或者 Map
     * @param dest 目标 Bean 对象
     * @param destType 目标的泛型类型，用于标注有泛型参数的 Bean 对象
     * @param copyOptions 拷贝属性选项
     */
    public BeanCopier(Object source, T dest, Type destType, CopyOptions copyOptions) {
        this.source = source;
        this.dest = dest;
        this.destType = destType;
        this.copyOptions = copyOptions;
    }

    /**
     * 创建 BeanCopier
     *
     * @param <T> 目标 Bean 类型
     * @param source 来源对象，可以是 Bean 或者 Map
     * @param dest 目标 Bean 对象
     * @param copyOptions 拷贝属性选项
     *
     * @return BeanCopier
     */
    public static <T> BeanCopier<T> create(Object source, T dest, CopyOptions copyOptions) {
        return create(source, dest, dest.getClass(), copyOptions);
    }

    /**
     * 创建 BeanCopier
     *
     * @param <T> 目标 Bean 类型
     * @param source 来源对象，可以是 Bean 或者 Map
     * @param dest 目标 Bean 对象
     * @param destType 目标的泛型类型，用于标注有泛型参数的 Bean 对象
     * @param copyOptions 拷贝属性选项
     *
     * @return BeanCopier
     */
    public static <T> BeanCopier<T> create(Object source, T dest, Type destType, CopyOptions copyOptions) {
        return new BeanCopier<>(source, dest, destType, copyOptions);
    }

    /**
     * 获取指定字段名对应的映射值
     *
     * @param mapping 反向映射 Map
     * @param fieldName 字段名
     *
     * @return 映射值，无对应值返回字段名
     */
    private static String mappingKey(Map<String, String> mapping, String fieldName) {
        if (MapUtils.isEmpty(mapping)) {
            return fieldName;
        }
        return ObjectUtils.defaultIfNull(mapping.get(fieldName), fieldName);
    }

    @Override
    public T copy() {
        if (null != this.source) {
            if (this.source instanceof ValueProvider) {
                // 目标只支持 Bean
                valueProviderToBean((ValueProvider<String>) this.source, this.dest);
            } else if (this.source instanceof Map) {
                if (this.dest instanceof Map) {
                    mapToMap((Map<?, ?>) this.source, (Map<?, ?>) this.dest);
                } else {
                    mapToBean((Map<?, ?>) this.source, this.dest);
                }
            } else {
                if (this.dest instanceof Map) {
                    beanToMap(this.source, (Map<?, ?>) this.dest);
                } else {
                    beanToBean(this.source, this.dest);
                }
            }
        }
        return this.dest;
    }

    /**
     * Bean 和 Bean 之间属性拷贝
     *
     * @param providerBean 来源 Bean
     * @param destBean 目标 Bean
     */
    private void beanToBean(Object providerBean, Object destBean) {
        valueProviderToBean(new BeanValueProvider(providerBean, this.copyOptions.ignoreCase, this.copyOptions.ignoreError), destBean);
    }

    /**
     * Map 转 Bean 属性拷贝
     *
     * @param map Map
     * @param bean Bean
     */
    private void mapToBean(Map<?, ?> map, Object bean) {
        valueProviderToBean(new MapValueProvider(map, this.copyOptions.ignoreCase), bean);
    }

    /**
     * Map 转 Map
     *
     * @param source 源 Map
     * @param dest 目标 Map
     */
    private void mapToMap(Map source, Map dest) {
        if (null != dest && null != source) {
            dest.putAll(source);
        }
    }

    /**
     * 对象转 Map
     *
     * @param bean bean 对象
     * @param targetMap 目标的 Map
     */
    private void beanToMap(Object bean, Map targetMap) {
        final Collection<BeanDesc.PropDesc> props = BeanUtils.getBeanDesc(bean.getClass()).getProps();
        final HashSet<String> ignoreSet = (null != copyOptions.ignoreProperties) ? CollectionUtils.newHashSet(copyOptions.ignoreProperties) : null;
        final CopyOptions copyOptions = this.copyOptions;

        String key;
        Method getter;
        Object value;
        for (BeanDesc.PropDesc prop : props) {
            key = prop.getFieldName();
            // 过滤 class 属性
            // 得到 property 对应的 getter 方法
            getter = prop.getGetter();
            if (null != getter) {
                // 只读取有 getter 方法的属性
                try {
                    value = getter.invoke(bean);
                } catch (Exception e) {
                    if (copyOptions.ignoreError) {
                        continue;// 忽略反射失败
                    } else {
                        throw new UtilsException(e, "Get value of [{}] error!", prop.getFieldName());
                    }
                }
                if (CollectionUtils.contains(ignoreSet, key)) {
                    // 目标属性值被忽略或值提供者无此 key 时跳过
                    continue;
                }
                if (null == value && copyOptions.ignoreNullValue) {
                    continue;// 当允许跳过空时，跳过
                }
                if (bean.equals(value)) {
                    continue;// 值不能为 bean 本身，防止循环引用
                }
                targetMap.put(mappingKey(copyOptions.fieldMapping, key), value);
            }
        }
    }

    /**
     * 值提供器转 Bean
     *
     * @param valueProvider 值提供器
     * @param bean Bean
     */
    private void valueProviderToBean(ValueProvider<String> valueProvider, Object bean) {
        if (null == valueProvider) {
            return;
        }

        final CopyOptions copyOptions = this.copyOptions;
        Class<?> actualEditable = bean.getClass();
        if (null != copyOptions.editable) {
            // 检查限制类是否为 target 的父类或接口
            if (!copyOptions.editable.isInstance(bean)) {
                throw new IllegalArgumentException(StringUtils.format("Target class [{}] not assignable to Editable class [{}]", bean.getClass().getName(), copyOptions.editable.getName()));
            }
            actualEditable = copyOptions.editable;
        }
        final HashSet<String> ignoreSet = (null != copyOptions.ignoreProperties) ? CollectionUtils.newHashSet(copyOptions.ignoreProperties) : null;
        final Map<String, String> fieldReverseMapping = copyOptions.getReversedMapping();

        final Collection<BeanDesc.PropDesc> props = BeanUtils.getBeanDesc(actualEditable).getProps();
        Field field;
        String fieldName;
        Object value;
        Method setterMethod;
        Class<?> propClass;
        for (BeanDesc.PropDesc prop : props) {
            // 获取值
            field = prop.getField();
            fieldName = prop.getFieldName();
            if (CollectionUtils.contains(ignoreSet, fieldName)) {
                // 目标属性值被忽略或值提供者无此 key 时跳过
                continue;
            }
            final String providerKey = mappingKey(fieldReverseMapping, fieldName);
            if (!valueProvider.containsKey(providerKey)) {
                // 无对应值可提供
                continue;
            }
            setterMethod = prop.getSetter();
            if (null == setterMethod && !ModifierUtils.isPublic(field)) {
                // Setter 方法不存在或者字段为非 public 跳过
                continue;
            }

            Type valueType = (null == setterMethod) ? TypeUtils.getType(field) : TypeUtils.getFirstParamType(setterMethod);
            if (valueType instanceof ParameterizedType) {
                // 参数为泛型参数类型，解析对应泛型类型为真实类型
                ParameterizedType tmp = (ParameterizedType) valueType;
                Type[] actualTypeArguments = tmp.getActualTypeArguments();
                if (TypeUtils.hasTypeVariable(actualTypeArguments)) {
                    // 泛型对象中含有未被转换的泛型变量
                    actualTypeArguments = TypeUtils.getActualTypes(this.destType, field.getDeclaringClass(), tmp.getActualTypeArguments());
                    if (ArrayUtils.isNotEmpty(actualTypeArguments)) {
                        // 替换泛型变量为实际类型
                        valueType = new ParameterizedTypeImpl(actualTypeArguments, tmp.getOwnerType(), tmp.getRawType());
                    }
                }
            } else if (valueType instanceof TypeVariable) {
                // 参数为泛型，查找其真实类型（适用于泛型方法定义于泛型父类）
                valueType = TypeUtils.getActualType(this.destType, field.getDeclaringClass(), valueType);
            }

            value = valueProvider.value(providerKey, valueType);
            if (null == value && copyOptions.ignoreNullValue) {
                continue;// 当允许跳过空时，跳过
            }
            if (bean.equals(value)) {
                continue;// 值不能为 bean 本身，防止循环引用
            }

            try {
                // valueProvider 在没有对值做转换且当类型不匹配的时候，执行默认转换
                propClass = prop.getFieldClass();
                if (!propClass.isInstance(value)) {
                    value = Convert.convert(propClass, value);
                    if (null == value && copyOptions.ignoreNullValue) {
                        continue;// 当允许跳过空时，跳过
                    }
                }

                if (null == setterMethod) {
                    // 直接注入值
                    ReflectUtils.setFieldValue(bean, field, value);
                } else {
                    // 执行 set 方法注入值
                    setterMethod.invoke(bean, value);
                }
            } catch (Exception e) {
                if (!copyOptions.ignoreError) {
                    throw new UtilsException(e, "Inject [{}] error!", prop.getFieldName());
                }
                // 忽略注入失败
            }
        }
    }

}
